#include "stdafx.h"
#include "Texture3D.h"
#include <d3dx10.h>

using namespace System::Runtime::InteropServices;
using namespace Reign::Core;

namespace Reign
{namespace Video
{namespace D3D10
{
	#pragma region Properties
	ID3D10ShaderResourceView* Texture3D::ShaderResource::get() {return shaderResource;}
	I32::Vector3 Texture3D::Size::get() {return size;}
	F32::Vector3 Texture3D::SizeF::get() {return sizeF;}
	F32::Vector3 Texture3D::TexelOffset::get() {return texelOffset;}
	#pragma endregion

	#pragma region Constructors
	Texture3D::Texture3D(DisposableI^ parent, string^ fileName)
	: Disposable(parent)
	{
		init(parent, fileName, 0, 0, 0, 0, MultiSampleTypes::None, SurfaceFormats::RGBAx8, BufferUsages::Default, false);
	}

	Texture3D::Texture3D(DisposableI^ parent, int width, int height, int depth, bool generateMipmaps, MultiSampleTypes multiSampleType, SurfaceFormats surfaceFormat, BufferUsages usage)
	: Disposable(parent)
	{
		init(parent, nullptr, width, height, depth, generateMipmaps, multiSampleType, surfaceFormat, usage, false);
	}

	void Texture3D::init(DisposableI^ parent, string^ fileName, int width, int height, int depth, bool generateMipmaps, MultiSampleTypes multiSampleType, SurfaceFormats surfaceFormat, BufferUsages usage, bool isRenderTarget)
	{
		null();
		try
		{
			video = parent->FindParentOrSelfWithException<Video^>();
			this->surfaceFormat = surfaceFormat;
			DXGI_FORMAT surfaceFormatD3D = Video::surfaceFormat(surfaceFormat);

			if (isRenderTarget) generateMipmaps = false;
		
			if (fileName)
			{
				uint mipLvls = generateMipmaps ? D3DX10_DEFAULT : D3DX10_FROM_FILE;
				D3DX10_IMAGE_LOAD_INFO desc;
				ZeroMemory(&desc, sizeof(D3DX10_IMAGE_LOAD_INFO));
				desc.Width = width;
				desc.Height = height;
				desc.Depth = depth;
				desc.MipLevels = mipLvls;
				desc.Filter = D3DX10_FILTER_TRIANGLE;
				desc.MipFilter = D3DX10_FILTER_TRIANGLE;
				desc.Format = surfaceFormatD3D;
				desc.Usage = D3D10_USAGE_DEFAULT;
				desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
				if (isRenderTarget) desc.BindFlags |= D3D10_BIND_RENDER_TARGET;

				ID3D10Resource* resourceTEMP;
				IntPtr fileNamePtr = Marshal::StringToHGlobalUni(fileName);
				if (FAILED(D3DX10CreateTextureFromFile(video->Device, (wchar_t*)fileNamePtr.ToPointer(), &desc, 0, &resourceTEMP, 0)))
				{
					Marshal::FreeHGlobal(fileNamePtr);
					Debug::ThrowError(L"Texture3D", L"Could not load texture: " + fileName);
				}
				texture = (ID3D10Texture3D*)resourceTEMP;

				// Get FileInfo
				D3DX10_IMAGE_INFO imageInfo;
				ZeroMemory(&imageInfo, sizeof(D3DX10_IMAGE_INFO));
				if (FAILED(D3DX10GetImageInfoFromFile((wchar_t*)fileNamePtr.ToPointer(), 0, &imageInfo, 0)))
				{
					Marshal::FreeHGlobal(fileNamePtr);
					Debug::ThrowError(L"Texture3D", L"Could get texture info: " + fileName);
				}
				size.X = imageInfo.Width;
				size.Y = imageInfo.Height;
				Marshal::FreeHGlobal(fileNamePtr);
			}
			else
			{
				uint mipLvls = generateMipmaps ? 0 : 1;
				D3D10_TEXTURE3D_DESC desc;
				ZeroMemory(&desc, sizeof(D3D10_TEXTURE3D_DESC));
				desc.Width = width;
				desc.Height = height;
				desc.Depth = depth;
				desc.MipLevels = mipLvls;
				desc.Format = surfaceFormatD3D;
				desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
				if (isRenderTarget) desc.BindFlags |= D3D10_BIND_RENDER_TARGET;

				desc.Usage = D3D10_USAGE_DEFAULT;
				if (usage == BufferUsages::Read)
				{
					desc.Usage = D3D10_USAGE_STAGING;
					desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ;
				}

				if (usage == BufferUsages::Write)
				{
					desc.Usage = D3D10_USAGE_DYNAMIC;
					desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
				}

				ID3D10Texture3D* textureTEMP;
				if (FAILED(video->Device->CreateTexture3D(&desc, 0, &textureTEMP)))
				{
					Debug::ThrowError(L"Texture3D", L"Failed to create Texture3D");
				}
				texture = textureTEMP;
				size.X = width;
				size.Y = height;
			}
		
			// ShaderResource
			D3D10_TEXTURE3D_DESC textureDesc;
			ZeroMemory(&textureDesc, sizeof(D3D10_TEXTURE3D_DESC));
			texture->GetDesc(&textureDesc);
			D3D10_SHADER_RESOURCE_VIEW_DESC srvDesc;
			ZeroMemory(&srvDesc, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));
			srvDesc.Format = surfaceFormatD3D;
			srvDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE3D;
			srvDesc.Texture3D.MipLevels = textureDesc.MipLevels;
			srvDesc.Texture3D.MostDetailedMip = 0;

			ID3D10ShaderResourceView* shaderResourceTEMP;
			if (FAILED(video->Device->CreateShaderResourceView(texture, &srvDesc, &shaderResourceTEMP)))
			{
				Debug::ThrowError(L"Texture3D", L"Failed to create ShaderResourceView");
			}
			shaderResource = shaderResourceTEMP;

			sizeF = Reign::Core::Extensions::ToFloat3(size);
		}
		catch (Exception^ ex)
		{
			delete this;
			throw ex;
		}
	}

	Texture3D::~Texture3D()
	{
		disposeChilderen();
		if (texture) texture->Release();
		if (shaderResource) shaderResource->Release();
		null();
	}

	void Texture3D::null()
	{
		texture = 0;
		shaderResource = 0;
		video = nullptr;
	}
	#pragma endregion

	#pragma region Methods
	void Texture3D::Update(array<byte>^ data)
	{
		pin_ptr<byte> dataT = &data[0];
		video->Device->UpdateSubresource(texture, D3D10CalcSubresource(0, 0, 1), 0, dataT, sizeof(byte)*4*size.X, sizeof(byte)*4*size.Z);
	}

	void Texture3D::UpdateDynamic(array<byte>^ data)
	{
		D3D10_MAPPED_TEXTURE3D buff;
		texture->Map(D3D10CalcSubresource(0, 0, 1), D3D10_MAP_WRITE_DISCARD, 0, &buff);
		pin_ptr<byte> dataT = &data[0];
		memcpy(buff.pData, dataT, data->Length);
		texture->Unmap(D3D10CalcSubresource(0, 0, 1));
	}

	void Texture3D::Copy(Texture3DI^ texture)
	{
		ID3D10Texture3D* dstTexture = ((Texture3D^)texture)->texture;
		video->Device->CopyResource(dstTexture, this->texture);
	}

	array<System::Byte>^ Texture3D::Copy()
	{
		D3D10_MAPPED_TEXTURE3D buff;
		texture->Map(D3D10CalcSubresource(0, 0, 1), D3D10_MAP_READ_WRITE, 0, &buff);
		array<System::Byte>^ data = gcnew array<System::Byte>(size.X * size.Y * size.Z * 4);
		pin_ptr<byte> dataT = &data[0];
		memcpy(dataT, buff.pData, data->Length);
		texture->Unmap(D3D10CalcSubresource(0, 0, 1));

		return data;
	}
	#pragma endregion
}
}
}